package fileservice

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	"yunj/pkg/global"
	"yunj/pkg/yunj/app/service"
	"yunj/pkg/yunj/core/response"
	"yunj/pkg/yunj/util"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type file struct {
}

var File *file

// 上传
func (s *file) Upload(ctx *gin.Context) any {
	origin := util.QueryParam(ctx, "origin", "input")
	oc := GetOriginConfig(origin)
	// 获取文件
	file, err := ctx.FormFile(oc.GetRequestKey())
	if err != nil {
		return oc.FailResponseData(fmt.Errorf("请选择文件"))
	}
	// 校验文件
	fileType, err := s.checkUploadFile(file)
	if err != nil {
		return oc.FailResponseData(err)
	}
	// 保存文件
	url, err := s.saveUploadFile(ctx, file, fileType, oc)
	if err != nil {
		return oc.FailResponseData(err)
	}
	// 返回成功的响应结果
	return oc.SuccessResponseData(url)
}

// 保存上传文件
func (s *file) saveUploadFile(ctx *gin.Context, file *multipart.FileHeader, fileType string, oc *originConfig) (url string, err error) {
	saveFullDir, saveFilename := oc.GetSavePath(file, fileType)
	// 确保目录存在，不存在则创建
	if err = util.EnsureDir(saveFullDir); err != nil {
		err = fmt.Errorf("创建保存目录失败: %v", err)
		return
	}
	// 保存上传文件
	dst := filepath.Join(saveFullDir, saveFilename)
	if err = ctx.SaveUploadedFile(file, dst); err != nil {
		err = fmt.Errorf("保存上传文件失败: %v", err)
		return
	}
	// 获取访问地址
	uri := strings.TrimPrefix(dst, "storage")
	uri = regexp.MustCompile(`\\+`).ReplaceAllString(uri, "/")
	url = global.Config.App.Url + uri
	return
}

// 校验上传文件
func (s *file) checkUploadFile(file *multipart.FileHeader) (fileType string, err error) {
	// 检查文件扩展名
	allowAllExtRules := service.GetAllowAllExtRules()
	ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(file.Filename)), ".")
	// fmt.Printf("1111|%v|%v\r\n", ext, allowAllExtRules)
	allowRule, exists := allowAllExtRules[ext]
	if !exists {
		err = fmt.Errorf("不支持%s类型的文件上传", ext)
		return
	}

	// 打开文件读取前512字节来检测MIME类型和魔数
	src, err := file.Open()
	if err != nil {
		err = fmt.Errorf("无法读取上传文件内容")
		return
	}
	defer src.Close()
	// 读取前512字节 - 这是检测MIME类型所需的最大字节数
	buffer := make([]byte, 512)
	n, err := src.Read(buffer)
	if err != nil && err != io.EOF {
		err = fmt.Errorf("无法读取上传文件的内容")
		return
	}
	// 重置文件指针以确保后续操作正常
	if _, err = src.Seek(0, io.SeekStart); err != nil {
		err = fmt.Errorf("无法重置上传文件指针")
		return
	}

	// 检测魔数
	if len(allowRule.MagicBytes) > 0 {
		magicMatch := false
		for _, magic := range allowRule.MagicBytes {
			if len(buffer) >= len(magic) && bytes.Equal(buffer[:len(magic)], magic) {
				magicMatch = true
				break
			}
		}
		if !magicMatch {
			err = fmt.Errorf("上传文件内容与扩展名不匹配")
			return
		}
	}
	// 检查MIME
	contentType := http.DetectContentType(buffer[:n])
	// 验证MIME类型
	mimeValid := false
	for _, allowMime := range allowRule.MIMETypes {
		// 允许MIME类型带参数（如 text/plain; charset=utf-8）
		if strings.HasPrefix(contentType, allowMime+";") || contentType == allowMime {
			mimeValid = true
			break
		}
		// 特殊处理ZIP格式的文件
		if strings.Contains(allowMime, "zip") && contentType == "application/zip" {
			mimeValid = true
			break
		}
	}
	if !mimeValid {
		err = fmt.Errorf("上传文件MIME类型不匹配，预期 %s，但检测到 %s", strings.Join(allowRule.MIMETypes, ", "), contentType)
		return
	}

	// 检查文件大小
	if strings.HasPrefix(allowRule.MIMETypes[0], "image/") {
		// 图片
		fileType = "img"
		allowUploadImageSize := service.GetAllowUploadImageSize()
		if file.Size > allowUploadImageSize*1024*1024 {
			err = fmt.Errorf("上传图片大小超出限制，最大允许 %dMB", allowUploadImageSize)
			return
		}
	} else {
		// 文件
		fileType = "file"
		allowUploadFileSize := service.GetAllowUploadFileSize()
		if file.Size > allowUploadFileSize*1024*1024 {
			err = fmt.Errorf("上传文件大小超出限制，最大允许 %dMB", allowUploadFileSize)
			return
		}
	}
	return
}

// 来源配置
type originConfig struct {
	requestKey          string
	path                func(file *multipart.FileHeader) (dir, filename string) // 获取保存文件夹和文件名
	failResponseData    func(err error) any
	successResponseData func(url string) any
}

// 获取请求参数
func (o *originConfig) GetRequestKey() string {
	if o.requestKey == "" {
		return "file"
	}
	return o.requestKey
}

// 获取保存文件路径
func (o *originConfig) GetSavePath(file *multipart.FileHeader, fileType string) (fullDir, filename string) {
	var fun func(file *multipart.FileHeader) (dir, filename string)
	if o.path != nil {
		fun = o.path
	} else {
		fun = func(file *multipart.FileHeader) (dir, filename string) {
			filename = util.Md5(uuid.New().String()) + strings.ToLower(filepath.Ext(file.Filename))
			return
		}
	}
	dir, filename := fun(file)
	now := time.Now()
	fullDir = filepath.Join(
		"storage/upload",
		fileType,
		fmt.Sprintf("%d", now.Year()),
		fmt.Sprintf("%02d", now.Month()),
		fmt.Sprintf("%02d", now.Day()),
	)
	if dir != "" {
		fullDir = filepath.Join(fullDir, dir)
	}
	return
}

// 失败响应数据
func (o *originConfig) FailResponseData(err error) any {
	var fun func(err error) any
	if o.failResponseData != nil {
		fun = o.failResponseData
	} else {
		fun = func(err error) any {
			return response.FailJsonData(err.Error(), 30001)
		}
	}
	return fun(err)
}

// 成功响应数据
func (o *originConfig) SuccessResponseData(url string) any {
	var fun func(url string) any
	if o.successResponseData != nil {
		fun = o.successResponseData
	} else {
		fun = func(url string) any {
			return response.SuccessJsonData(map[string]any{
				"originName": filepath.Base(url),
				"url":        url,
			})
		}
	}
	return fun(url)
}

var originConfigs = map[string]*originConfig{
	"input": {
		successResponseData: func(url string) any {
			return response.SuccessJsonData(map[string]any{
				"fileName": filepath.Base(url),
				"url":      url,
			})
		},
	},
	"nameUnchanged": {
		path: func(file *multipart.FileHeader) (dir string, filename string) {
			dir = util.Md5(uuid.New().String())
			filename = file.Filename
			return
		},
		successResponseData: func(url string) any {
			return response.SuccessJsonData(url)
		},
	},
	"ckeditor": {
		requestKey: "upload",
		failResponseData: func(err error) any {
			return map[string]any{
				"error": map[string]any{
					"message": err.Error(),
					"number":  500,
				},
			}
		},
		successResponseData: func(url string) any {
			return map[string]any{
				"fileName": filepath.Base(url),
				"uploaded": 1,
				"url":      url,
			}
		},
	},
}

func GetOriginConfig(key string) *originConfig {
	if c, exists := originConfigs[key]; exists {
		return c
	}
	return &originConfig{}
}
